import crypto from 'crypto'
import { ToolConfig } from '../types'

// Function to encode S3 path components
function encodeS3PathComponent(pathComponent: string): string {
  return encodeURIComponent(pathComponent).replace(/%2F/g, '/')
}

// Function to calculate AWS signature key
function getSignatureKey(
  key: string,
  dateStamp: string,
  regionName: string,
  serviceName: string
): Buffer {
  if (!key || typeof key !== 'string') {
    throw new Error('Invalid key provided to getSignatureKey')
  }
  const kDate = crypto
    .createHmac('sha256', 'AWS4' + key)
    .update(dateStamp)
    .digest()
  const kRegion = crypto.createHmac('sha256', kDate).update(regionName).digest()
  const kService = crypto.createHmac('sha256', kRegion).update(serviceName).digest()
  const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest()
  return kSigning
}

function parseS3Uri(s3Uri: string): { bucketName: string; region: string; objectKey: string } {
  try {
    const url = new URL(s3Uri)
    const hostname = url.hostname
    const bucketName = hostname.split('.')[0]
    const regionMatch = hostname.match(/s3[.-]([^.]+)\.amazonaws\.com/)
    const region = regionMatch ? regionMatch[1] : 'us-east-1'
    const objectKey = url.pathname.startsWith('/') ? url.pathname.substring(1) : url.pathname

    if (!bucketName || !objectKey) {
      throw new Error('Invalid S3 URI format')
    }

    return { bucketName, region, objectKey }
  } catch (error) {
    throw new Error(
      'Invalid S3 Object URL format. Expected format: https://bucket-name.s3.region.amazonaws.com/path/to/file'
    )
  }
}

// Function to generate a pre-signed URL
function generatePresignedUrl(params: any, expiresIn: number = 3600): string {
  const date = new Date()
  const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, '')
  const dateStamp = amzDate.slice(0, 8)
  const encodedPath = encodeS3PathComponent(params.objectKey)

  // Set expiration time
  const expires = Math.floor(Date.now() / 1000) + expiresIn

  // Create the canonical request
  const method = 'GET'
  const canonicalUri = `/${encodedPath}`
  const canonicalQueryString = `X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${encodeURIComponent(params.accessKeyId + '/' + dateStamp + '/' + params.region + '/s3/aws4_request')}&X-Amz-Date=${amzDate}&X-Amz-Expires=${expiresIn}&X-Amz-SignedHeaders=host`
  const canonicalHeaders = 'host:' + params.bucketName + '.s3.' + params.region + '.amazonaws.com\n'
  const signedHeaders = 'host'
  const payloadHash = 'UNSIGNED-PAYLOAD'

  const canonicalRequest =
    method +
    '\n' +
    canonicalUri +
    '\n' +
    canonicalQueryString +
    '\n' +
    canonicalHeaders +
    '\n' +
    signedHeaders +
    '\n' +
    payloadHash

  // Create string to sign
  const algorithm = 'AWS4-HMAC-SHA256'
  const credentialScope = dateStamp + '/' + params.region + '/s3/aws4_request'
  const stringToSign =
    algorithm +
    '\n' +
    amzDate +
    '\n' +
    credentialScope +
    '\n' +
    crypto.createHash('sha256').update(canonicalRequest).digest('hex')

  // Calculate signature
  const signingKey = getSignatureKey(params.secretAccessKey, dateStamp, params.region, 's3')
  const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex')

  // Create signed URL
  return `https://${params.bucketName}.s3.${params.region}.amazonaws.com/${encodedPath}?${canonicalQueryString}&X-Amz-Signature=${signature}`
}

// Get Object Tool
export const s3GetObjectTool: ToolConfig = {
  id: 's3_get_object',
  name: 'S3 Get Object',
  description: 'Retrieve an object from an AWS S3 bucket',
  version: '2.0.0',
  params: {
    accessKeyId: {
      type: 'string',
      required: true,
      description: 'Your AWS Access Key ID',
    },
    secretAccessKey: {
      type: 'string',
      required: true,
      description: 'Your AWS Secret Access Key',
    },
    s3Uri: {
      type: 'string',
      required: true,
      description: 'S3 Object URL (e.g., https://bucket-name.s3.region.amazonaws.com/path/to/file)',
    },
  },
  request: {
    url: (params) => {
      try {
        const { bucketName, region, objectKey } = parseS3Uri(params.s3Uri)

        params.bucketName = bucketName
        params.region = region
        params.objectKey = objectKey

        return `https://${bucketName}.s3.${region}.amazonaws.com/${encodeS3PathComponent(objectKey)}`
      } catch (error) {
        throw new Error(
          'Invalid S3 Object URL format. Expected format: https://bucket-name.s3.region.amazonaws.com/path/to/file'
        )
      }
    },
    method: 'HEAD',
    headers: (params) => {
      try {
        // Parse S3 URI if not already parsed
        if (!params.bucketName || !params.region || !params.objectKey) {
          const { bucketName, region, objectKey } = parseS3Uri(params.s3Uri)
          params.bucketName = bucketName
          params.region = region
          params.objectKey = objectKey
        }

        // Use UTC time explicitly
        const date = new Date()
        const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, '')
        const dateStamp = amzDate.slice(0, 8)

        const method = 'HEAD'
        const encodedPath = encodeS3PathComponent(params.objectKey)
        const canonicalUri = `/${encodedPath}`
        const canonicalQueryString = ''
        const payloadHash = crypto.createHash('sha256').update('').digest('hex')
        const host = `${params.bucketName}.s3.${params.region}.amazonaws.com`
        const canonicalHeaders =
          `host:${host}\n` + `x-amz-content-sha256:${payloadHash}\n` + `x-amz-date:${amzDate}\n`
        const signedHeaders = 'host;x-amz-content-sha256;x-amz-date'
        const canonicalRequest =
          method +
          '\n' +
          canonicalUri +
          '\n' +
          canonicalQueryString +
          '\n' +
          canonicalHeaders +
          '\n' +
          signedHeaders +
          '\n' +
          payloadHash

        const algorithm = 'AWS4-HMAC-SHA256'
        const credentialScope = dateStamp + '/' + params.region + '/s3/aws4_request'
        const stringToSign =
          algorithm +
          '\n' +
          amzDate +
          '\n' +
          credentialScope +
          '\n' +
          crypto.createHash('sha256').update(canonicalRequest).digest('hex')

        const signingKey = getSignatureKey(params.secretAccessKey, dateStamp, params.region, 's3')
        const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex')

        const authorizationHeader =
          algorithm +
          ' ' +
          'Credential=' +
          params.accessKeyId +
          '/' +
          credentialScope +
          ', ' +
          'SignedHeaders=' +
          signedHeaders +
          ', ' +
          'Signature=' +
          signature

        return {
          Host: host,
          'X-Amz-Content-Sha256': payloadHash,
          'X-Amz-Date': amzDate,
          Authorization: authorizationHeader,
        }
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown error'
        throw new Error('Failed to generate request headers: ' + errorMessage)
      }
    },
  },
  transformResponse: async (response: Response, params) => {
    try {
      if (!response.ok) {
        throw new Error(`S3 request failed: ${response.status} ${response.statusText}`)
      }

      // Parse S3 URI if not already parsed
      if (!params.bucketName || !params.region || !params.objectKey) {
        const { bucketName, region, objectKey } = parseS3Uri(params.s3Uri)
        params.bucketName = bucketName
        params.region = region
        params.objectKey = objectKey
      }

      // Get file metadata
      const contentType = response.headers.get('content-type') || 'application/octet-stream'
      const contentLength = parseInt(response.headers.get('content-length') || '0', 10)
      const lastModified = response.headers.get('last-modified') || new Date().toISOString()
      const fileName = params.objectKey.split('/').pop() || params.objectKey

      // Generate pre-signed URL for download
      const url = generatePresignedUrl(params, 3600)

      return {
        success: true,
        output: {
          url,
          metadata: {
            fileType: contentType,
            size: contentLength,
            name: fileName,
            lastModified: lastModified,
          },
        },
      }
    } catch (error: unknown) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error'
      return {
        success: false,
        output: {
          url: '',
          metadata: {
            fileType: 'error',
            size: 0,
            name: params.objectKey?.split('/').pop() || 'unknown',
            error: errorMessage,
          },
        },
      }
    }
  },
}
